
import random

_base_buckets = [ (0, 0),
         (-1, 0), (-0.5, 1), (0.5, 1), (1, 0), (0.5, -1), (-0.5, -1),
         (-2, 0), (-1.5, 1), (-1, 2), (0, 2), (1, 2), (1.5, 1), (2, 0),
         (1.5, -1), (1, -2), (0, -2), (-1, -2), (-1.5, -1) ]


def gen_buckets_simple(center, bucket_radius, separation_fraction):
    for e in _base_buckets:
        pos = center + complex(*e)*bucket_radius*(1.0 + separation_fraction)
        yield pos
    raise StopIteration

def gen_buckets_loose(center, bucket_radius, separation_fraction):
    for e in _base_buckets:
        pos = center + complex(*e)*bucket_radius*(1.0 + separation_fraction)
        dpos = complex( random.random()*0.5*separation_fraction,
                        random.random()*0.5*separation_fraction)
        pos += dpos
        yield pos
    raise StopIteration

def gen_buckets_simple_random(center, bucket_radius, separation_fraction):
    buckets = list(_base_buckets)
    random.shuffle(buckets)
    for e in buckets:
        pos = center + complex(*e)*bucket_radius*(1.0 + separation_fraction)
        yield pos
    raise StopIteration

def gen_buckets_loose_random(center, bucket_radius, separation_fraction):
    buckets = list(_base_buckets)
    random.shuffle(buckets)
    for e in buckets:
        pos = center + complex(*e)*bucket_radius*(1.0 + separation_fraction)
        dpos = complex( random.random()*0.5*separation_fraction,
                        random.random()*0.5*separation_fraction)
        pos += dpos
        yield pos
    raise StopIteration
            
def infinite_generator(generator, *params):
    while 1:
        _gen = generator(*params)
        for v in _gen:
            yield v

def test1():
    center = complex(3,5)
    gen = gen_buckets_simple(center, 2.0, 1.0)
    for pos in gen:
        print pos

def test2():
    center = complex(3,5)
    gen = gen_buckets_simple(center, 2.0, 1.0)
    bContinue = True
    while bContinue:
        try:
            print gen.next()
        except StopIteration:
            bContinue = False

def test3():
    center = complex(3,5)
    gen = infinite_generator(gen_buckets_simple, center, 2.0, 1.0)
    for i in xrange(len(_base_buckets)+1):
        print gen.next()

##############################################################################

# container which clasifies its content in several categories
# if element e is in the container, it belongs to only one category
# warn: it injects member .category to the elements
class CategoryCollection(object):
    def __init__(self):
        self.categories = {}

    def remove(self, element):
        try:
            old_category = element.category
            self.categories[old_category].remove(element)
            delattr(element, 'category')
        except AttributeError:
            pass

    def update(self, new_category, element):
        try:
            old_category = element.category
            self.categories[old_category].remove(element)
        except AttributeError:
            pass
        element.category = new_category
        if new_category not in self.categories:
            self.categories[new_category] = set()
        self.categories[new_category].add(element)

    def get_safe(self, category):
        # it is safe to change categories while iterating for e in obj.getsafe(..)
        # also it is safe to remove elements
        if category not in self.categories:
            self.categories[category] = set()
        return list(self.categories[category])

    def get_unsafe(self, category):
        if category not in self.categories:
            self.categories[category] = set()
        return self.categories[category]

    def __iter__(self):
        for cat in self.categories.keys():
            colection = self.get_safe(cat)
            for e in colection:
                yield e
        # note:
        #   all elements are seen if the for block at most changes the category
        #   for the yielded element
        #   some elements can be omited in a situation like:
        #   for e in clasif:
        #       other = ...
        #       clasif.update('aaa',other)
        #   it is safe to change categories into the iteration
        #   it is safe to remove elements into the iteration
        #   some elements can be seen more than one time ( only if changing
        #   categories while iterating )

        
    def iter_all_unsafe(self):
        # useful for updates that doesn't changes elements category (nor remove
        # elements)
        for cat in self.categories:
            for e in self.categories[cat]:
                yield e

    def __len__(self):
        n = 0
        for cat in self.categories:
            n += len(self.categories[cat])
        return n

    def catlen(self, cat):
        try:
            n = len(self.categories[cat])
        except KeyError:
            n = 0
        return n

def test_CategoryCollection():
    class A(object):
        def __init__(self,x):
            self.x = x

    coll = CategoryCollection()
    e = A(0)
    coll.update('idle',e)
    e = A(1)
    coll.update('follower',e)
    e = A(2)
    coll.update('goalized',e)

    for e in coll:
        print 'cat, x:', e.category, e.x

    print
    it = coll.iter_all_unsafe()
    for e in it:
        print 'cat, x:', e.category, e.x

    for e in coll:
        coll.update('idle',e)

    print
    it = coll.iter_all_unsafe()
    for e in it:
        print 'cat, x:', e.category, e.x
        
    for e in coll:
        to_remove = e
        break

    print '\nremoving e / e.x=',to_remove.x
    coll.remove(to_remove)
    it = coll.iter_all_unsafe()
    for e in it:
        print 'cat, x:', e.category, e.x
    assert not hasattr(to_remove, 'category')

    print '\nremoving all from the for'
    for e in coll:
        coll.remove(e)
    print 'coll contents>'
    for e in it:
        print 'cat, x:', e.category, e.x
    print '<coll contents'
        
##############################################################################

def discrete_8way(x, y):
# [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)]
    if x <= 0:
        sx = -1; x = -x
    else:
        sx = 1
    if y <= 0:
        sy = -1; y = -y
    else:
        sy = 1
    return ((y <= 2*x)*sx, (2*y >= x)*sy) 

def test_discrete_8way():
    import math
    for a in xrange(0, 360, 5):
        x, y = math.cos(math.radians(a)), math.sin(math.radians(a))
        print 'a, x,y:', a, discrete_8way(x,y)

##    for a in xrange(0, 360, 45):
##        x, y = math.cos(math.radians(a)), math.sin(math.radians(a))
##        print discrete_8way(x,y), ',',
        
        


if __name__ == '__main__':
##    test1()
##    test2()
##    test3()
##    test_CategoryCollection()
    test_discrete_8way()
